#include "../pch.h"
#include "REFXMLLoader.h"
#include "schema/REFXMLSchemaInfo.h"
#include "core/REFXMLTextReaderImpl.h"

namespace BFX
{

REFXMLLoader::REFXMLLoader()
{
	m_bPreserveWhitespace = false;
	m_pDoc = NULL;
}


REFXMLLoader::~REFXMLLoader()
{
}

bool REFXMLLoader::Load(REFXMLDocument* pDoc, REFXMLReader* pReader, bool bPreserveWhitespace)
{
	BFX_REQUIRE (pDoc != NULL);
	BFX_REQUIRE (pReader != NULL);

	m_pDoc		= pDoc;
	m_pReader	= pReader;
	m_bPreserveWhitespace	= bPreserveWhitespace;

	m_pDoc->SetBaseURI(pReader->GetBaseURI());
	if (m_pReader->GetReadState() != ReadState_Interactive)
	{
		if (!m_pReader->Read())
			return false;
	}
	return LoadDocSequence(pDoc);
}

bool REFXMLLoader::IsErrorOccurred() const
{
	return m_pReader->IsErrorOccurred();
}

// Start loading the document from where current xmlreader is pointing at.
bool REFXMLLoader::LoadDocSequence(REFXMLDocument* pParentDoc)
{
	BFX_ASSERT (pParentDoc != NULL);
	BFX_ASSERT (m_pReader != NULL);

	REFXMLNode* pNode;
	while ((pNode = LoadNode(true)) != NULL)
	{
		pParentDoc->AppendChildForLoad(pNode, pParentDoc);
		if (!m_pReader->Read())
			break;
	}
	return !IsErrorOccurred();
}

REFXMLNode* REFXMLLoader::LoadNode(bool bSkipOverWhitespace)
{
	REFXMLReader* r = m_pReader;
	REFXMLNode* pParent = NULL;
	REFXMLElement* pElement = NULL;

	do
	{
		// NOTE: check error state for each loop.
		if (IsErrorOccurred())
		{
			return NULL;
		}

		REFXMLNode* pNode = NULL;
		switch (r->GetNodeType())
		{
		case XMLNodeType_Element:
			{
				bool bEmptyElement = r->IsEmptyElement();
				pElement = m_pDoc->CreateElement(r->GetPrefix(), r->GetLocalName(), r->GetNamespaceURI());
				// pElement->SetIsEmpty(bEmptyElement);
				// load attributes.
				if (r->MoveToFirstAttribute())
				{
					REFXMLAttributeMap* pAttrs = pElement->GetAttributes();
					do
					{
						REFXMLAttribute* pAttr = LoadAttributeNode();
						pAttrs->Append(pAttr);	// special case for load
					}
					while (r->MoveToNextAttribute());
					r->MoveToElement();
				}
				// TODO: Sould we check the error state here?

				// recursively load all children.
				if (!bEmptyElement)
				{
					if (pParent != NULL)
						pParent->AppendChildForLoad(pElement, m_pDoc);
					pParent = pElement;
					continue;
				}
				else
				{
					// TODO: schema info
#if	0
					pSchemaInfo = r->GetSchemaInfo();
					if (pSchemaInfo != NULL)
					{
						pElement->SetXmlName(m_pDoc->AddXmlName(pElement->GetPrefix(), pElement->GetLocalName(), pElement->GetNamespaceURI(), pSchemaInfo);
					}
#endif
					pNode = pElement;
					break;
				}
			}

		case XMLNodeType_EndElement:
			if (pParent == NULL)
				return NULL;
			BFX_ASSERT (pParent->GetNodeType() == XMLNodeType_Element);
			// TODO: schema info
#if	0
			pSchemaInfo = r->GetSchemaInfo();
			if (pSchemaInfo != NULL && pParent->GetNodeType() == XMLNodeType_Element)
			{
				pElement = (REFXMLElement*)pParent;
				pElement->SetXmlName(m_pDoc->AddXmlName(pElement->GetPrefix(), pElement->GetLocalName(), pElement->GetNamespaceURI(), pSchemaInfo);
			}
#endif
			if (pParent->GetParentNode() == NULL)
				return pParent;
			pParent = pParent->GetParentNode();
			continue;

		case XMLNodeType_EntityReference:
			pNode = LoadEntityReferenceNode();
			break;

		case XMLNodeType_EndEntity:
			BFX_ASSERT(pParent == NULL);
			return NULL;

		case XMLNodeType_Attribute:
			pNode = LoadAttributeNode();
			break;

		case XMLNodeType_Text:
			pNode = m_pDoc->CreateTextNode(r->GetValue());
			break;

		case XMLNodeType_SignificantWhitespace:
			// TODO: pNode = m_pDoc->CreateSignificantWhitespace(r->GetValue());
			BFX_ASSERT(false);
			break;
		case XMLNodeType_Whitespace:
			if (m_bPreserveWhitespace)
			{
				BFX_ASSERT(false);
				// TODO: pNode = m_pDoc->CreateWhitespace(r->GetValue());
				break;
			}
			else if (pParent == NULL && !bSkipOverWhitespace)
			{
				// if called from LoadEntityReferenceNode, just return NULL
				return NULL;
			}
			else
			{
				continue;
			}
		case XMLNodeType_CDATA:
			pNode = m_pDoc->CreateCDataSection(r->GetValue());
			break;

		case XMLNodeType_XmlDeclaration:
			pNode = LoadDeclarationNode();
			break;

		case XMLNodeType_ProcessingInstruction:
			pNode = m_pDoc->CreateProcessingInstruction(r->GetName(), r->GetValue());
			break;

		case XMLNodeType_Comment:
			pNode = m_pDoc->CreateComment(r->GetValue());
			break;

		case XMLNodeType_DocumentType:
			pNode = LoadDocumentTypeNode();
			break;

		default:
			BFX_ENSURE1(false, "Unexpected XmlNodeType.");	//	InvalidOperationException
			return NULL;
		}

		BFX_ASSERT (pNode != NULL);
		if (pParent != NULL)
			pParent->AppendChildForLoad(pNode, m_pDoc);
		else
			return pNode;
	}
	while (r->Read());

	return NULL;
}

REFXMLAttribute* REFXMLLoader::LoadAttributeNode()
{
	BFX_ASSERT(m_pReader->GetNodeType() == XMLNodeType_Attribute);

	REFXMLReader* r = m_pReader;
	if (r->IsDefault())
	{
		return LoadDefaultAttribute();
	}

	REFXMLAttribute* pAttr = m_pDoc->CreateAttribute(r->GetPrefix(), r->GetLocalName(), r->GetNamespaceURI());
	// TODO: schema info

	LoadAttributeValue(pAttr);

	return pAttr;
}

REFXMLAttribute* REFXMLLoader::LoadDefaultAttribute()
{
	REFXMLReader* r = m_pReader;
	BFX_ASSERT(r->IsDefault());

	REFXMLAttribute* pAttr = m_pDoc->CreateDefaultAttribute(r->GetPrefix(), r->GetLocalName(), r->GetNamespaceURI());
	// TODO: schema info

	LoadAttributeValue(pAttr);

	// TODO: unspecified attribute???

	return pAttr;
}

void REFXMLLoader::LoadAttributeValue(REFXMLNode* pParent)
{
	REFXMLReader* r = m_pReader;
	while (r->ReadAttributeValue())
	{
		REFXMLNode* pNode;
		switch (r->GetNodeType())
		{
		case XMLNodeType_Text:
			pNode = m_pDoc->CreateTextNode(r->GetValue());
			break;
		case XMLNodeType_EndEntity:
			return;
		case XMLNodeType_EntityReference:
			pNode = m_pDoc->CreateEntityReference(r->GetLocalName());
			// TODO: resolve entity.
			break;
		default:
			BFX_ENSURE1(false, "Unexpected XmlNodeType.");	//	InvalidOperationException
			return;
		}
		BFX_ASSERT(pNode != NULL);
		pParent->AppendChildForLoad(pNode, m_pDoc);
	}
}

REFXMLEntityReference* REFXMLLoader::LoadEntityReferenceNode()
{
	BFX_ASSERT(m_pReader->GetNodeType() == XMLNodeType_EntityReference);
	REFXMLEntityReference* pEntRef = m_pDoc->CreateEntityReference(m_pReader->GetName());
	// TODO: 
#if	0
	if (m_pReader->CanResolveEntity())
	{
		m_pReader->ResolveEntity();
		while (m_pReader->Read() && m_pReader->GetNodeType() != XMLNodeType_EndEntity)
		{
			REFXMLNode* pNode = LoadNode(false);
			if (pNode != NULL)
			{
				pEntRef->AppendChildForLoad(pNode, m_pDoc);
			}
		}
		// Code internally relies on the fact that an EntRef nodes has at least one child (even an empty text node). Ensure that this holds true,
		// if the reader does not present any children for the ent-ref
		if (pEntRef->GetLastChild() == NULL)
			pEntRef->AppendChildForLoad(m_pDoc->CreateTextNode(UniString()), m_pDoc);
	}
#endif
	return pEntRef;
}

REFXMLDeclaration* REFXMLLoader::LoadDeclarationNode()
{
	BFX_ASSERT(m_pReader->GetNodeType() == XMLNodeType_XmlDeclaration);

	//parse data
	UniString strVersion;
	UniString strEncoding;
	UniString strStandalone;

	// Try first to use the reader to get the xml decl "attributes". Since not all readers are required to support this, it is possible to have
	// implementations that do nothing
	while(m_pReader->MoveToNextAttribute())
	{
		UniString strName = m_pReader->GetName();
		if (strName == USTR("version"))
			strVersion = m_pReader->GetValue();
		else if (strName == USTR("encoding"))
			strEncoding = m_pReader->GetValue();
		else if (strName == USTR("standalone"))
			strStandalone = m_pReader->GetValue();
		else
			BFX_ASSERT(false);
	}
	// For readers that do not break xml decl into attributes, we must parse the xml decl ourselfs. We use version attr, b/c xml decl MUST contain
	// at least version attr, so if the reader implements them as attr, then version must be present
	if (strVersion.IsEmpty())
	{
		// TODO: in case of value = "version='1.0' encoding='utf-8' standalone='yes' ", we need to parse them manually!
		// but should never happen here.
		// ParseXmlDeclarationValue(m_pReader->GetValue(), OUT strVersion, OUT strEncoding, OUT strStandalone);
		BFX_ASSERT(false);
	}

	return this->m_pDoc->CreateXmlDeclaration(strVersion, strEncoding, strStandalone);
}

REFXMLDocumentType* REFXMLLoader::LoadDocumentTypeNode()
{
	BFX_ASSERT(m_pReader->GetNodeType() == XMLNodeType_DocumentType);
	
	UniString strPublicId;
	UniString strSystemId;
	UniString strInternalSubset = m_pReader->GetValue();
	UniString strLocalName = m_pReader->GetLocalName();

	while (m_pReader->MoveToNextAttribute())
	{
		UniString strName = m_pReader->GetName();
		if (strName == USTR("PUBLIC"))
			strPublicId = strName;
		else if (strName == USTR("SYSTEM"))
			strSystemId = strName;
	}

	REFXMLDocumentType* pDtNode = m_pDoc->CreateDocumentType(strLocalName, strPublicId, strSystemId, strInternalSubset);

	REFXMLSchemaInfo* pSchemaInfo = m_pReader->GetDtdSchemaInfo();
	if (pSchemaInfo != NULL)
		LoadDocumentType(pSchemaInfo, pDtNode);
	else
	{
		// TODO: construct our own XmlValidatingReader to parse the DocumentType node so we could get entities and notations information
		// TODO: ParseDocumentType(pDtNode);
		BFX_ASSERT(false);
	}

	return pDtNode;
}

void REFXMLLoader::LoadDocumentType(REFXMLSchemaInfo* pDtdSchemaInfo, REFXMLDocumentType* pDtNode)
{
	pDtNode->SetDtdSchemaInfo(pDtdSchemaInfo);
	if (pDtdSchemaInfo != NULL)
	{
		// set the schema information into the document
		m_pDoc->SetDtdSchemaInfo(pDtdSchemaInfo);

		// Notation hashtable
		if (pDtdSchemaInfo->GetNotations() != NULL)
		{
			for (REFXMLSchemaNotationMap::POSITION entry = pDtdSchemaInfo->GetNotations()->GetFirstEntry();
				entry != NULL; entry = pDtdSchemaInfo->GetNotations()->GetNextEntry(entry))
			{
				REFXMLSchemaNotation* pNotation = entry->value;
				pDtNode->GetNotations()->SetNamedItem(new REFXMLNotation(pNotation->GetName()->GetName(), pNotation->GetPubid(),
					pNotation->GetSystemLiteral(), m_pDoc));
			}
		}
		// Entity hashtables
		if (pDtdSchemaInfo->GetGeneralEntities() != NULL)
		{
			for (REFXMLSchemaEntityMap::POSITION entry = pDtdSchemaInfo->GetGeneralEntities()->GetFirstEntry();
				entry != NULL; entry = pDtdSchemaInfo->GetGeneralEntities()->GetNextEntry(entry))
			{
				REFXMLSchemaEntity* pEntity = entry->value;
				REFXMLEntity* pEnt = new REFXMLEntity(pEntity->GetName()->GetName(), pEntity->GetText(), pEntity->GetPubid(), pEntity->GetUrl(),
					pEntity->GetNData()->IsEmpty() ? UniString() : pEntity->GetNData()->GetName(), m_pDoc);
				pEnt->SetBaseURI(pEntity->GetDeclaredURI());
				pDtNode->GetEntities()->SetNamedItem(pEnt);
			}
		}

		if (pDtdSchemaInfo->GetParameterEntities() != NULL)
		{
			for (REFXMLSchemaEntityMap::POSITION entry = pDtdSchemaInfo->GetParameterEntities()->GetFirstEntry();
				entry != NULL; entry = pDtdSchemaInfo->GetParameterEntities()->GetNextEntry(entry))
			{
				REFXMLSchemaEntity* pEntity = entry->value;
				REFXMLEntity* pEnt = new REFXMLEntity(pEntity->GetName()->GetName(), pEntity->GetText(), pEntity->GetPubid(), pEntity->GetUrl(),
					pEntity->GetNData()->IsEmpty() ? UniString() : pEntity->GetNData()->GetName(), m_pDoc);
				pEnt->SetBaseURI(pEntity->GetDeclaredURI());
				pDtNode->GetEntities()->SetNamedItem(pEnt);
			}
		}
		m_pDoc->SetEntities(pDtNode->GetEntities());

		// extract the elements which has attribute defined as ID from the element declarations
		if (pDtdSchemaInfo->GetElementDecls() != NULL)
		{
			for (REFXMLSchemaElementDeclMap::POSITION entry = pDtdSchemaInfo->GetElementDecls()->GetFirstEntry();
				entry != NULL; entry = pDtdSchemaInfo->GetElementDecls()->GetNextEntry(entry))
			{
				REFXMLSchemaElementDecl* pElementDecl = entry->value;
				if (pElementDecl->GetAttDefs() != NULL)
				{
					for (REFXMLSchemaAttDefMap::POSITION attrEntry = pElementDecl->GetAttDefs()->GetFirstEntry();
						attrEntry != NULL; attrEntry = pElementDecl->GetAttDefs()->GetNextEntry(attrEntry))
					{
						REFXMLSchemaAttDef* pAttrDef = attrEntry->value;
						if (pAttrDef->GetDataType() == XMLTokenizedType_ID)
						{
							// we only register the XmlElement based on their Prefix/LocalName and skip the namespace
							m_pDoc->AddElementIdInfo(m_pDoc->AddXmlName(pElementDecl->GetPrefix(), pElementDecl->GetName()->GetName(), UniString()/*, NULL*/),
								m_pDoc->AddAttrXmlName(pAttrDef->GetPrefix(), pAttrDef->GetName()->GetName(), UniString()/*, NULL*/));
							break;
						}
					}
				}
			}
		}
	}

}

}	//	namespace BFX
